/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "../guidefaults.h"
#include "../DXGUIRenderer.h"

namespace gui {

extern IGUIRenderer* g_pGUIRenderer;

bool coreInitDXGUIRenderer( LPDIRECT3DDEVICE9 pd3dDevice )
{	
	if (!g_pGUIRenderer) {
		// Create just one GUIRenderer
		g_pGUIRenderer = new DXGUIRenderer();
	}
	// Always update the DirectX device pointer
	dynamic_cast<DXGUIRenderer*>(g_pGUIRenderer)->pd3dDevice = pd3dDevice;
	return true;
}

bool coreDestroyDXGUIRenderer()
{
	if (!g_pGUIRenderer) return false;
	DELETE_POINTER(g_pGUIRenderer);
	return true;
}

DXGUIRenderer::DXGUIRenderer(void)
:	pRectBuffer(NULL),
	pRectBuffer2(NULL),
	pFilledRectBuffer(NULL),
	pRectDecl(NULL),
	pStateBlock(NULL),
	pOldStateBlock(NULL),
	pCircleBuffer(NULL),
	pFilledCircleBuffer(NULL),
	lastSegments(0),
	pVertices(NULL),
	pCircleVertices(NULL),
	pFilledCircleVertices(NULL)
{
}

DXGUIRenderer::~DXGUIRenderer(void)
{
	RELEASE(pRectBuffer);
	RELEASE(pRectBuffer2);
	RELEASE(pFilledRectBuffer);
	RELEASE(pCircleBuffer);
	RELEASE(pFilledCircleBuffer);
	DELETE_ARRAY(pCircleVertices);
	DELETE_ARRAY(pFilledCircleVertices);
	RELEASE(pRectDecl);
	RELEASE(pStateBlock);
	RELEASE(pOldStateBlock);
}

void DXGUIRenderer::clear()
{
	pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_COLORVALUE(0, 0, 0, 0), 0.0f, 0);
}

void DXGUIRenderer::createVertexDecl()
{
	// Create the vertex declaration
	D3DVERTEXELEMENT9 decl[] = {
		{0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},	// Position
		{1, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0},	// Color
		{2, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},	// TEXCOORDS
		D3DDECL_END()
	};
	pd3dDevice->CreateVertexDeclaration(decl, &pRectDecl);
}

void DXGUIRenderer::drawRect(int x, int y, int width, int height, const math::Vector4 &color)
{
	ScreenVertex fullVertices[] = 
	{
			//	X		Y		Z	RTW		COLRO		U		V
			{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
			{static_cast<float>(x+width),	static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+width),	static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x),			static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
	};

	if (!pRectBuffer) {
		// Create a vertex buffer
		pd3dDevice->CreateVertexBuffer( 5 * sizeof( ScreenVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 
										D3DFVF_SCREENVERTEX,
										D3DPOOL_DEFAULT, 
										&pRectBuffer, 
										NULL );
	}

	// Fill the buffer
	pRectBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, fullVertices, sizeof(fullVertices) );
	pRectBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pRectBuffer, 0, sizeof( ScreenVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP,  0, 4 );
}

void DXGUIRenderer::drawRoundRect(int x, int y, int width, int height, const math::Vector4 &color)
{
	ScreenVertex fullVertices[] = 
	{
			//	X		Y		Z	RTW		COLRO		U		V
			{static_cast<float>(x+2),		static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
			{static_cast<float>(x+width-2),	static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+width),	static_cast<float>(y+2),		0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+width),	static_cast<float>(y+height-2),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+width-2),	static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+2),		static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x),			static_cast<float>(y+height-2),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x),			static_cast<float>(y+2),		0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x+2),		static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
	};

	if (!pRectBuffer2) {
		// Create a vertex buffer
		pd3dDevice->CreateVertexBuffer( 9 * sizeof( ScreenVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 
										D3DFVF_SCREENVERTEX,
										D3DPOOL_DEFAULT, 
										&pRectBuffer2, 
										NULL );
	}

	// Fill the buffer
	pRectBuffer2->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, fullVertices, sizeof(fullVertices) );
	pRectBuffer2->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pRectBuffer2, 0, sizeof( ScreenVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP,  0, 8 );
}


void DXGUIRenderer::drawFilledRect(int x, int y, int width, int height, const math::Vector4 &color)
{
	ScreenVertex fullVertices[] = 
	{
	//	X		Y		Z		RHW		U		V
			{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
			{static_cast<float>(x+width),	static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
			{static_cast<float>(x),			static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   1.0f },
			{static_cast<float>(x+width),	static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   1.0f },
	};

	if (!pFilledRectBuffer) {
		pd3dDevice->CreateVertexBuffer( 4 * sizeof( ScreenVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_SCREENVERTEX,
										D3DPOOL_DEFAULT, &pFilledRectBuffer, NULL );
	}

	pFilledRectBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, fullVertices, sizeof(fullVertices) );
	pFilledRectBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pFilledRectBuffer, 0, sizeof( ScreenVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
}

void DXGUIRenderer::drawTexturedRect(int x, int y, int width, int height, int texX, int texY)
{
}

void DXGUIRenderer::drawCircle(int x, int y, float radius, int segments, const math::Vector4 &color)
{
	if (lastSegments != segments) 
	{
		DELETE_ARRAY(pCircleVertices);

		pCircleVertices = new ScreenVertex[segments];
		RELEASE(pCircleBuffer);
		pd3dDevice->CreateVertexBuffer( segments * sizeof( ScreenVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_SCREENVERTEX,
										D3DPOOL_DEFAULT, &pCircleBuffer, NULL );

		lastSegments = segments;
	}

	float deg = 0;
	float degStep = 2*PI/(segments-1);
	for (int i=0; i<segments; ++i) {
		pCircleVertices[i].x = x + cos(deg) * radius;
		pCircleVertices[i].y = y + sin(deg) * radius;
		pCircleVertices[i].rhw = 1.0f;
		pCircleVertices[i].tu = 0;
		pCircleVertices[i].tv = 0;
		pCircleVertices[i].color = D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w);
		deg += degStep;
	}

	pCircleBuffer->Lock( 0, sizeof(ScreenVertex)*segments, (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, pCircleVertices, sizeof(ScreenVertex)*segments );
	pCircleBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pCircleBuffer, 0, sizeof( ScreenVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP,  0, segments-1 );
}

void DXGUIRenderer::drawFilledCircle(int x, int y, float radius, int segments, const math::Vector4 &color)
{
	segments += 1;

	if (lastSegments != segments) 
	{
		DELETE_ARRAY(pFilledCircleVertices);

		pFilledCircleVertices = new ScreenVertex[segments];
		RELEASE(pFilledCircleBuffer);
		pd3dDevice->CreateVertexBuffer( segments * sizeof( ScreenVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_SCREENVERTEX,
										D3DPOOL_DEFAULT, &pFilledCircleBuffer, NULL );

		lastSegments = segments;
	}
	// First point in the middle
	pFilledCircleVertices[0].x = static_cast<float>(x);
	pFilledCircleVertices[0].y = static_cast<float>(y);
	pFilledCircleVertices[0].rhw = 1.0f;
	pFilledCircleVertices[0].tu = 0;
	pFilledCircleVertices[0].tv = 0;
	pFilledCircleVertices[0].color = D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w);
	
	// The others in a circle around
	float deg = 0;
	float degStep = 2*PI/(segments-2);
	for (int i=1; i<segments; ++i) {
		pFilledCircleVertices[i].x = x + cos(deg) * radius;
		pFilledCircleVertices[i].y = y + sin(deg) * radius;
		pFilledCircleVertices[i].rhw = 1.0f;
		pFilledCircleVertices[i].tu = 0;
		pFilledCircleVertices[i].tv = 0;
		pFilledCircleVertices[i].color = D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w);
		deg += degStep;
	}

	pFilledCircleBuffer->Lock( 0, sizeof(ScreenVertex)*segments, (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, pFilledCircleVertices, sizeof(ScreenVertex)*segments );
	pFilledCircleBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pFilledCircleBuffer, 0, sizeof( ScreenVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN,  0, segments-2 );
}

void DXGUIRenderer::enableGUIState()
{	
	RELEASE(pOldStateBlock);
	pd3dDevice->CreateStateBlock( D3DSBT_ALL, &pOldStateBlock );

	//if (!pStateBlock) {
		//pd3dDevice->BeginStateBlock();
		pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
		pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
		pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
		pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE );
		pd3dDevice->SetRenderState( D3DRS_SEPARATEALPHABLENDENABLE, FALSE );
		pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD );
		pd3dDevice->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED );
		pd3dDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD ); 
		pd3dDevice->SetRenderState( D3DRS_FOGENABLE, FALSE );
		pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
		pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
		pd3dDevice->SetRenderState( D3DRS_ZENABLE, FALSE );
		pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); 
	//	pd3dDevice->EndStateBlock( &pStateBlock);
	//}

	//if (pStateBlock) 
	//	pStateBlock->Apply();
}

void DXGUIRenderer::disableGUIState()
{
	if (pOldStateBlock) 
		pOldStateBlock->Apply();
}

math::Vector2i DXGUIRenderer::getScreenSize()
{
	LPDIRECT3DSURFACE9 pBackBuffer;
	D3DSURFACE_DESC backBufferDesc;

	pd3dDevice->GetRenderTarget(0, &pBackBuffer);
	pBackBuffer->GetDesc( &backBufferDesc );
	return math::Vector2i(static_cast<int>(backBufferDesc.Width), static_cast<int>(backBufferDesc.Height));
}

} // namespace
